﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Drawing;
using System.Web;
using System.Xml.Linq;
using System.Text;
using System.IO;
using System.Globalization;

namespace MplusM.MovieDatabaseWorkers
{
	/// <summary>
	/// class presents all the necessary methods to utilise the CSFD service for obtaining media details
	/// </summary>
    public class CSFDWorker : IMovieDatabaseWorker
    {
        private string url;
        private Encoding encoding;
        public event WorkerContentHandler ContentLoaded;
        public event WorkerErrorHandler ContentLoadedFailed;
        public event WorkerImageHandler CoverLoaded;
        public event WorkerErrorHandler CoverLoadedFailed;

        public CSFDWorker()
        {
            this.url = "";
            this.encoding = Encoding.UTF8;
        }

        public CSFDWorker(string url)
            : this()
        {
            this.url = url;
        }

        public string Url
        {
            get
            {
                return url;
            }
            set
            {
                url = value;
            }
        }

        public Encoding Encoding
        {
            get
            {
                return encoding;
            }
            set
            {
                encoding = value;
            }
        }

        #region public methods
		/// <summary>
		/// Loads contents in a new thread from CSFD asynchronously in a new thread
		/// </summary>
        public void LoadContentAsync()
        {
            System.Threading.Thread t = new System.Threading.Thread(getContent);
            t.Start(this.url);
        }
		/// <summary>
		/// Loads media cover asynchronously from CSFD in a new thread
		/// </summary>
        public void LoadCoverAsync()
        {
            System.Threading.Thread t = new System.Threading.Thread(getCover);
            t.Start(this.url);
        }

        #endregion

        #region public static methods
		/// <summary>
		/// generates a URL to obtain media details of a given title
		/// </summary>
		/// <param name="searchTitle">a title to be searched for in the CSFD database</param>
		/// <returns>string containing a URL to be used as a query for result</returns>
        public static String CreateSearchUrl(String searchTitle)
        {
            return "http://www.csfd.cz/hledat/?q=" + searchTitle.Replace(" ", "+");
        }

		/// <summary>
		/// generates a URL to obtain media cover of a given content
		/// </summary>
		/// <param name="content">a <value>string</value> containing a content of a certain media to be searched for Cover url</param>
		/// <returns>URL as <value>string</value> if a cover is found; null otherwise</returns>
        public static String GetCoverURL(String content)
        {
            int index = content.IndexOf("<meta property=\"og:image\" content=\"");
            if (index < 0)
            {
                return null;
            }
            index += "<meta property=\"og:image\" content=\"".Length;
            int length = content.IndexOf("\" />", index) - index;
            if (length <= 0)
            {
                return null;
            }
            return content.Substring(index, length);
        }

		/// <summary>
		/// parses the content provided by CSFD into a <c>MovingPicture</c> class.
		/// </summary>
		/// <param name="content">a single movie result from CSFD</param>
		/// <returns>parsed <c>MovingPicture</c> class representing the result found in CSFD databse</returns>
        public static MovingPicture GetMovieDetails(String content)
        {
            MovingPicture result = new MovingPicture();
            // translated name
            int index = content.IndexOf("<h1>");
            if (index >= 0)
            {
                int length = content.IndexOf("</h1>") - content.IndexOf("<h1>");
                if (length > 0)
                {
                    Regex reg = new Regex("<h1>|</h1>");
                    result.TranslatedName = reg.Replace(content.Substring(index, length), "").Trim();
                }
            }
            // original name
            index = content.IndexOf("<ul class=\"names\">");
            if (index >= 0)
            {
                int length = content.IndexOf("</ul>", index) - index + 5;
                if (length > 0)
                {
                    XElement names = null;
                    try
                    {
                        names = XElement.Parse(content.Substring(index, length));
                    }
                    catch (Exception)
                    {
                        // do nothing
                    }
                    if (names != null && names.Element("li") != null)
                    {
                        result.OriginalName = names.Element("li").Element("h3").Value;
                    }
                }
            }
            // length, country, release year
            index = content.IndexOf("<p class=\"origin\">");
            if (index >= 0)
            {
                index += "<p class=\"origin\">".Length;
                int length = content.IndexOf("</p>", index) - index;
                if (length >= 0)
                {
                    String origin = content.Substring(index, length);
                    String[] items = origin.Split(',');
                    if (items.Length == 3)
                    {
                        result.Country = items[0];
                        DateTime year;
                        if (DateTime.TryParse("1-1-" + items[1], new CultureInfo("en-us"), DateTimeStyles.AssumeUniversal, out year))
                        {
                            result.ReleaseDate = year;
                        }
                        ushort minutes;
                        if (ushort.TryParse(items[2].Replace("min", ""), out minutes))
                        {
                            result.Length = minutes;
                        }
                    }
                }
            }
            // director
            index = content.IndexOf("<h4>Režie:</h4>");
            if (index >= 0)
            {
                index += "<h4>Režie:</h4>".Length;
                int length = content.IndexOf("</span>", index) - index + 7;
                if (length > 0)
                {
                    XElement span = null;
                    try
                    {
                        span = XElement.Parse(content.Substring(index, length));
                    }
                    catch (Exception)
                    {
                        // do nothing
                    }
                    if (span != null && span.Element("a") != null)
                    {
                        result.Director = span.Element("a").Value;
                    }
                }
            }
            // rating
            index = content.IndexOf("<h2 class=\"average\">");
            if (index >= 0)
            {
                index += "<h2 class=\"average\">".Length;
                int length = content.IndexOf("</h2>", index) - index;
                String rating = content.Substring(index, length).Replace("%", "");
                short value;
                if (short.TryParse(rating, out value))
                {
                    result.Rating = (double)value / 10;
                }
            }
            // content
            index = content.IndexOf("<div data-truncate=\"570\">");
            if (index >= 0)
            {
                int length = content.IndexOf("</div>", index) - index + 6;
                if (length > 0)
                {
                    XElement area = null;
                    try
                    {
                        area = XElement.Parse(HttpUtility.HtmlDecode(content.Substring(index, length)));
                    }
                    catch (Exception)
                    {
                        // do nothing
                    }
                    if (area != null)
                    {
                        result.Content = area.Value.Trim();
                    }
                }
            }
            // genres
            index = content.IndexOf("<p class=\"genre\">");
            if (index >= 0)
            {
                index += "<p class=\"genre\">".Length;
                int length = content.IndexOf("</p>", index) - index;
                String genres = content.Substring(index, length);
                String[] genre = genres.Split('/');
                result.Genres = new List<string>();
                foreach (String g in genre)
                {
                    result.Genres.Add(g.Trim());
                }
            }
            // cast
            index = content.IndexOf("<span data-truncate=\"340\">");
            if (index >= 0)
            {
                int length = content.IndexOf("</span>", index) - index + 7;
                if (length > 0)
                {
                    XElement actors = null;
                    try
                    {
                        actors = XElement.Parse(HttpUtility.HtmlDecode(content.Substring(index, length)));
                    }
                    catch (Exception)
                    {
                        // do nothing
                    }
                    if (actors != null)
                    {
                        result.Cast = new List<string>();
                        foreach (XElement actor in actors.Elements("a"))
                        {
                            result.Cast.Add(actor.Value);
                        }
                    }
                }
            }
            return result;
        }

		/// <summary>
		/// returns a <c>Dictionary</c> containing all results corresponding to a given content searched in 
		/// the CSFD database
		/// </summary>
		/// <param name="content"></param>
		/// <returns>a <c>Dictionary</c> containing all results corresponding to a given content</returns>
        public static Dictionary<String, String> GetFoundTitles(String content)
        {
            int index = content.IndexOf("<div id=\"search-films\"");
            if (index < 0)
            {
                return null;
            }
            int length = content.IndexOf("<div id=\"search-creators\"") - content.IndexOf("<div id=\"search-films\"");
            if (length <= 0)
            {
                return null;
            }
            String found = content.Substring(index, length);
            XElement searchDiv = null;
            try
            {
                searchDiv = XElement.Parse(found);
            }
            catch (Exception)
            {
                return null;
            }
            Dictionary<string, string> result = new Dictionary<string, string>();
            foreach (XElement div in searchDiv.Elements("div"))
            {
                if (div.Attribute("class").Value.Equals("content"))
                {
                    foreach (XElement ul in div.Elements("ul"))
                    {
                        foreach (XElement li in ul.Elements("li"))
                        {
                            if (li.Element("h3") == null || li.Element("p") == null || li.Element("h3").Element("a") == null || li.Element("h3").Element("a").Attribute("href") == null)
                            {
                                break;
                            }
                            String name = li.Element("h3").Value + ": " + li.Element("p").Value;
                            String url = "http://www.csfd.cz" + li.Element("h3").Element("a").Attribute("href").Value;
                            if (!result.ContainsKey("url"))
                            {
                                result.Add(url, name);
                            }
                        }
                    }
                }
            }
            return result.Count == 0 ? null : result;
        }

        #endregion

        #region private methods
		/// <summary>
		/// retrieves the content from a given url and stores it in the <c>result</c> property
		/// </summary>
		/// <param name="url">the string containing a CSFD url with an expecting result</param>
        private void getContent(object url)
        {
            System.Net.WebClient webClient = new System.Net.WebClient();
            webClient.Encoding = encoding;
            String result = null;
            try
            {
                result = webClient.DownloadString((String)url);
            }
            catch (Exception ex)
            {
                onContentLoadedFailed(ex);
            }
            finally
            {
                webClient.Dispose();
            }
            if (result != null) onContentLoaded(result);
        }

		/// <summary>
		/// gets a cover from the given url and adds it to the result
		/// </summary>
		/// <param name="url">the url of a cover</param>
        private void getCover(object url)
        {
            System.Net.WebClient webClient = new System.Net.WebClient();
            Image img = null;
            try
            {
                byte[] data = webClient.DownloadData((String)url);
                img = Image.FromStream(new MemoryStream(data));
            }
            catch (Exception ex)
            {
                onCoverLoadedFailed(ex);
            }
            finally
            {
                webClient.Dispose();
            }
            if (img != null) onCoverLoaded(img);
        }

		/// <summary>
		/// an action to be triggered when the content is loaded
		/// </summary>
		/// <param name="content"></param>
        private void onContentLoaded(String content)
        {
            if (ContentLoaded != null)
            {
                ContentLoaded(content);
            }
        }

		/// <summary>
		/// an action to be triggered when the content load fails
		/// </summary>
		/// <param name="ex"></param>
        private void onContentLoadedFailed(Exception ex)
        {
            if (ContentLoadedFailed != null)
            {
                ContentLoadedFailed(ex);
            }
        }

		/// <summary>
		/// an action to be triggered when the cover is loaded
		/// </summary>
		/// <param name="img"></param>
        private void onCoverLoaded(Image img)
        {
            if (CoverLoaded != null)
            {
                CoverLoaded(img);
            }
        }

		/// <summary>
		/// an action to be triggered when the cover load fails
		/// </summary>
		/// <param name="ex"></param>
        private void onCoverLoadedFailed(Exception ex)
        {
            if (CoverLoadedFailed != null)
            {
                CoverLoadedFailed(ex);
            }
        }

        #endregion
    }

}